﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

using Storm.TextEditor.Core;
using Storm.TextEditor.Drawing.GDI;
using Storm.TextEditor.Win32;

namespace Storm.TextEditor.Editor
{
    /// <summary>
    /// Represents a list of all fonts in the Windows Forms Designer.
    /// </summary>
    public class FontList
        : UITypeEditor
    {
        #region Fields

        private bool handleLostfocus = false;
        private IWindowsFormsEditorService editorService = null;
        private ListBox fontListBox = null;

        #endregion

        #region Methods

        #region Private

        /// <summary>
        /// Called when [draw item].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
        private void OnDrawItem(object sender, DrawItemEventArgs e)
        {
            bool selected = (e.State & DrawItemState.Selected) == DrawItemState.Selected;

            if (e.Index == -1)
                return;

            object listBoxItem = fontListBox.Items[e.Index];
            string itemText = listBoxItem.ToString();

            Brush backgroundBrush = null;
            Brush foregroundBrush = null;

            if (selected == true)
            {
                backgroundBrush = SystemBrushes.Highlight;
                foregroundBrush = SystemBrushes.HighlightText;
            }
            else
            {
                backgroundBrush = SystemBrushes.Window;
                foregroundBrush = SystemBrushes.WindowText;
            }

            if (selected == true)
            {
                int xOffset = 37;
                e.Graphics.FillRectangle(SystemBrushes.Window, new Rectangle(xOffset, e.Bounds.Top, e.Bounds.Width - xOffset, fontListBox.ItemHeight));
                e.Graphics.FillRectangle(SystemBrushes.Highlight, new Rectangle(xOffset + 1, e.Bounds.Top + 1, e.Bounds.Width - xOffset - 2,
                    fontListBox.ItemHeight - 2));

                ControlPaint.DrawFocusRectangle(e.Graphics, new Rectangle(xOffset, e.Bounds.Top, e.Bounds.Width - xOffset, fontListBox.ItemHeight));
            }
            else
                e.Graphics.FillRectangle(SystemBrushes.Window, 0, e.Bounds.Top, e.Bounds.Width, fontListBox.ItemHeight);


            e.Graphics.DrawString(itemText, e.Font, foregroundBrush, 38, e.Bounds.Top + 4);
            e.Graphics.SetClip(new Rectangle(1, e.Bounds.Top + 2, 34, fontListBox.ItemHeight - 4));
            e.Graphics.FillRectangle(SystemBrushes.Highlight, new Rectangle(1, e.Bounds.Top + 2, 34, fontListBox.ItemHeight - 4));

            IntPtr graphicsHdc = e.Graphics.GetHdc();
            GDIFont itemFont = new GDIFont(itemText, 9);

            IntPtr res = NativeGdi32Api.SelectObject(graphicsHdc, itemFont.hFont);
            NativeGdi32Api.SetTextColor(graphicsHdc, ColorTranslator.ToWin32(SystemColors.Window));

            NativeGdi32Api.SetBkMode(graphicsHdc, 0);

            int tabStopPositions = 0;
            NativeUser32Api.TabbedTextOut(graphicsHdc, 3, e.Bounds.Top + 5, "abc", 3, 0, ref tabStopPositions, 0);

            NativeGdi32Api.SelectObject(graphicsHdc, res);

            itemFont.Dispose();
            e.Graphics.ReleaseHdc(graphicsHdc);
            e.Graphics.DrawRectangle(Pens.Black, new Rectangle(1, e.Bounds.Top + 2, 34, fontListBox.ItemHeight - 4));

            e.Graphics.ResetClip();
        }

        /// <summary>
        /// Called when [mouse down].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            if (handleLostfocus == false && fontListBox.ClientRectangle.Contains(fontListBox.PointToClient(new Point(e.X, e.Y))) == true)
            {
                fontListBox.LostFocus += new EventHandler(OnValueChanged);
                handleLostfocus = true;
            }
        }

        /// <summary>
        /// Called when [value changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnValueChanged(object sender, EventArgs e)
        {
            if (editorService != null)
                editorService.CloseDropDown();
        }

        #endregion

        #region Public

        /// <summary>
        /// Edits the specified object's value using the editor style indicated by the <see cref="M:System.Drawing.Design.UITypeEditor.GetEditStyle"/> method.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that can be used to gain additional context information.</param>
        /// <param name="provider">An <see cref="T:System.IServiceProvider"/> that this editor can use to obtain services.</param>
        /// <param name="value">The object to edit.</param>
        /// <returns>
        /// The new value of the object. If the value of the object has not changed, this should return the same object it was passed.
        /// </returns>
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            if (context != null && context.Instance != null && provider != null)
            {
                editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
                if (editorService != null)
                {
                    fontListBox = new ListBox();

                    fontListBox.DrawMode = DrawMode.OwnerDrawFixed;
                    fontListBox.BorderStyle = BorderStyle.None;
                    fontListBox.Sorted = true;

                    fontListBox.ItemHeight = 20;
                    fontListBox.Height = 200;
                    fontListBox.Width = 180;

                    fontListBox.MouseDown += OnMouseDown;
                    fontListBox.DoubleClick += OnValueChanged;
                    fontListBox.DrawItem += OnDrawItem;

                    ICollection fonts = new FontEnum().EnumFonts();
                    foreach (string font in fonts)
                        fontListBox.Items.Add(font);

                    editorService.DropDownControl(fontListBox);
                    if (fontListBox.SelectedItem != null)
                        return fontListBox.SelectedItem.ToString();
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the editor style used by the <see cref="M:System.Drawing.Design.UITypeEditor.EditValue(System.IServiceProvider,System.Object)"/> method.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that can be used to gain additional context information.</param>
        /// <returns>
        /// A <see cref="T:System.Drawing.Design.UITypeEditorEditStyle"/> value that indicates the style of editor used by the <see cref="M:System.Drawing.Design.UITypeEditor.EditValue(System.IServiceProvider,System.Object)"/> method. If the <see cref="T:System.Drawing.Design.UITypeEditor"/> does not support this method, then <see cref="M:System.Drawing.Design.UITypeEditor.GetEditStyle"/> will return <see cref="F:System.Drawing.Design.UITypeEditorEditStyle.None"/>.
        /// </returns>
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.DropDown;
        }

        /// <summary>
        /// Paints a representation of the value of an object using the specified <see cref="T:System.Drawing.Design.PaintValueEventArgs"/>.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Drawing.Design.PaintValueEventArgs"/> that indicates what to paint and where to paint it.</param>
        public override void PaintValue(PaintValueEventArgs e)
        {
            string text = e.Value.ToString();

            Bitmap bitmap = new Bitmap(e.Bounds.Width, e.Bounds.Height);
            Graphics graphics = Graphics.FromImage(bitmap);

            Brush backgroundBrush = SystemBrushes.Window;
            Brush foregroundBrush = SystemBrushes.WindowText;

            graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);

            IntPtr graphicsHdc = graphics.GetHdc();
            GDIFont itemFont = new GDIFont(text, 9);

            IntPtr selectObject = NativeGdi32Api.SelectObject(graphicsHdc, itemFont.hFont);
            NativeGdi32Api.SetTextColor(graphicsHdc, ColorTranslator.ToWin32(SystemColors.Window));

            NativeGdi32Api.SetBkMode(graphicsHdc, 0);

            int tabStopPositions = 0;
            NativeUser32Api.TabbedTextOut(graphicsHdc, 1, 1, "abc", 3, 0, ref tabStopPositions, 0);
            NativeGdi32Api.SelectObject(graphicsHdc, selectObject);

            itemFont.Dispose();
            graphics.ReleaseHdc(graphicsHdc);
            e.Graphics.DrawImage(bitmap, e.Bounds.Left, e.Bounds.Top);
        }

        /// <summary>
        /// Indicates whether the specified context supports painting a representation of an object's value within the specified context.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that can be used to gain additional context information.</param>
        /// <returns>
        /// true if <see cref="M:System.Drawing.Design.UITypeEditor.PaintValue(System.Object,System.Drawing.Graphics,System.Drawing.Rectangle)"/> is implemented; otherwise, false.
        /// </returns>
        public override bool GetPaintValueSupported(ITypeDescriptorContext context)
        {
            return true;
        }

        #endregion

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="FontList"/> class.
        /// </summary>
        public FontList()
        {
        }
    }
}
